package com.atguigu.tingshu.account.service.impl;


import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Autowired
    private UserAccountServiceImpl UserAccountServiceImpl;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 检查并锁定账户余额
     * @param accountLockVo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<AccountLockResultVo> checkAndLockUserAccount(AccountLockVo accountLockVo) {

        //校验用户的可用余额是否充足
        // 假设：50可用  50块钱
        // .校验这个用户的可用余额是否充足 如果充足 才扣减可用余额 + 锁定余额   否则这个用户的可用余额不充足 不用锁定和扣减 告诉你调用方锁定失败
        // sq1: select * from  user_account where user_id= accountLockVo.getUserId() and available_amount> accountLockVo.getAmount();
        // sql2: update  user_account set available_amount=available_amount-50,lock_amount=lock_amount+50  where user_id= accountLockVo.getUserId()
        // CAS思想保证判断和修改的原子操作（比较并交换）
        // sql3:update  user_account set available_amount=available_amount-50,lock_amount=lock_amount+50  where user_id= accountLockVo.getUserId() and  available_amount> accountLockVo.getAmount();



        // OpenFeign有重试机制(默认不会重试，你可以开启重试机制：默认提供了一个重试机制【5次】 自己定义重试机制：重试器)
        // 假设：重试5次   前三次在1min钟重试完  后面两次都是 每隔1min钟重试：3min重试五次
        // 如果你开启了openfeign的重试机制 一定要保证这个接口的幂等性。

        // 1.处理变量
        Long userId = accountLockVo.getUserId();
        String orderNo = accountLockVo.getOrderNo();
        String content = accountLockVo.getContent();
        BigDecimal amount = accountLockVo.getAmount();
        //1.2 定义key
        String dateKey = "user:account:result:"+orderNo;
        String lockKey = "user:account:lock:"+orderNo;

        // 2.检查和锁定账户余额（利用CAS思想的原子操作）

        //分布式锁 ,防止本接口被多次调用多次执行(极端情况,多次调用,一般使用opfeign都是单次调用)
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "c", 5, TimeUnit.SECONDS);

        if (!aBoolean){
            return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }

        int count=userAccountMapper.checkAndLockUserAccount(userId,amount);
        if (count==0){
            return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }

        //3.构建一个锁对象
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
        accountLockResultVo.setUserId(userId);
        accountLockResultVo.setAmount(amount);
        accountLockResultVo.setContent(content);

        //4.为了以后方便解锁,我们将锁对象放在Reids中
        redisTemplate.opsForValue().set(dateKey, JSONObject.toJSONString(accountLockResultVo));

        //5.将锁定的流水插入到流水表中(用户账户流水表)
        UserAccountServiceImpl.log(userId, "锁定"+content, "1202", amount, orderNo);


        return Result.ok(accountLockResultVo);
    }

    @Transactional(rollbackFor = Exception.class)
    public void log(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);

        userAccountDetailMapper.insert(userAccountDetail);

    }
}
